/*
	Copyright (C) 2010 Salil Bhagurkar

	This file is part of salil64

	salil64 is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	salil64 is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with salil64.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "types.h"
#include <kprint.h>
#include "asm.h"
#include <pagealloc.h>

#define PAGE_SIZE 4096


static inline unsigned long readCr3(void)
{
	unsigned long val;
	asm volatile("mov %%cr3,%0\n\t" : "=r" (val));
	return val;
}

static inline void writeCr3(unsigned long val)
{
	asm volatile("mov %0,%%cr3": : "r" (val));
}

static int isPageAligned(unsigned long address)
{
	if(address & 0xFFF) {
		return 0;
	}
	return 1;
}


static inline void setEntry(unsigned long *table, int index, unsigned long address, int present, int super, int write)
{
	table[index] = address | present | (write << 1) | (super << 2);
}

static inline unsigned long *getAddress(unsigned long entry)
{
	//Remove the 12 bits
	return (void *)(entry & (~0xFFF));
}

static inline unsigned long getAddress2(unsigned long entry)
{
	return (entry & (~0xFFF));
}

static inline int isPresent(unsigned long entry)
{
	return entry & 1;
}

static inline int isWriteable(unsigned long entry)
{
	return entry & 2;
}

static inline int isSuper(unsigned long entry)
{
	return entry & 4;
}

static inline int isFull(unsigned long entry)
{
	return entry & 6;
}


#define NULL (void *)0

//The pml4 for kernel
static unsigned long *pml4 = NULL;

#define SYSPAGE_STACK_SIZE 20

static void *sysPages[SYSPAGE_STACK_SIZE];
static int sysPagePtr = 0;

void pushSysPage(void *pageV)
{
	if(sysPagePtr >= SYSPAGE_STACK_SIZE) {
		kPrint("pushSysPage: Stack overflow\n");
		return;
	}
	sysPages[sysPagePtr++] = pageV;
}

void *popSysPage()
{
	if(sysPagePtr <= 0)
		return NULL;
	return sysPages[--sysPagePtr];
}

int getSysPagePtr()
{
	return sysPagePtr;
}


#define PML4_MASK (((unsigned long)0x1ff) << 39)
#define PDPT_MASK (0x1ff << 30)
#define PD_MASK (0x1ff << 21)
#define PT_MASK (0x1ff << 12)


int mapPage(void *pageV, void *addressV)
{
	unsigned long page = (unsigned long)pageV;
	unsigned long address = (unsigned long)addressV;
	//Figure out where to map the page from 'page'
	unsigned long pml4Index = (page & PML4_MASK) >> 39;
	unsigned long pdptIndex = (page & PDPT_MASK) >> 30;
	unsigned long pdIndex = (page & PD_MASK) >> 21;
	unsigned long ptIndex = (page & PT_MASK) >> 12;

	//Find out if a pml4 is present at this index
	if(!pml4[pml4Index]) {
		//Get a page from the stack
		void *page = popSysPage();
		if(!page)
			return -1;
		setEntry(pml4, pml4Index, (unsigned long)page, 1, 1, 1);
		kPrint("mapPage: pml4 pop\n");
	}
	unsigned long *pdpt = getAddress(pml4[pml4Index]);

	if(!pdpt[pdptIndex]) {
		void *page = popSysPage();
		if(!page)
			return -1;
		setEntry(pdpt, pdptIndex, (unsigned long)page, 1, 1, 1);
		kPrint("mapPage: pdpt pop\n");
	}
	unsigned long *pd = getAddress(pdpt[pdptIndex]);

	if(!pd[pdIndex]) {
		void *page = popSysPage();
		if(!page)
			return -1;
		setEntry(pd, pdIndex, (unsigned long)page, 1, 1, 1);
		kPrint("mapPage: pd pop\n");
	}
	unsigned long *pt = getAddress(pd[pdIndex]);

	//Now map the page finally
	setEntry(pt, ptIndex, getAddress2(address), 1, 1, 1);
	return 0;
}

void updatePaging()
{
	writeCr3((unsigned long)pml4);
}


void initPaging()
{
	//Initialize our pml4 to the address of the pml4 setup in boot.s
	pml4 = &_pml4;
	updatePaging();

	pageAllocSetup();
}
